// ****************************************************************
// Copyright 2012, Stephan Burguchev
// e-mail: me@sburg.net
// ****************************************************************
// *
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using Updater.Common;
using Version = Updater.Utils.Version;

namespace Updater.tmp
{
    /// <summary>
    ///     The class provides an information required for applying an update and implement methods to perform the update.
    /// </summary>
    /// <remarks>
    ///     <para>
    ///         An update is splitted by executing stages.
    ///     </para>
    ///     <para>
    ///         A first stage is the background stage. The stage is performing while the installed version of
    ///         the application is still running. It is desirable to perform as much of required actions as possible
    ///         at this stage because of this part of update is running on background and the user can work while
    ///         the stage is performing. To this stage the updater have already downloaded the update to
    ///         <see cref="UpdateArchiveFilepath" /> file and extracted it to
    ///         <see cref="UnpackedUpdateDirectory" /> directory.
    ///     </para>
    ///     <para>
    ///         A second stage is the launcher startup stage. The stage is performing after the application
    ///         shutdown and allowing the updater to update the resources locked while the application executing.
    ///     </para>
    /// </remarks>
    public class Update
    {
        /// <summary>
        ///     Initializes a new instance of the <see cref="Update" /> class.
        /// </summary>
        /// <param name="versionFrom">
        ///     The version the update should be applied to.
        /// </param>
        /// <param name="versionTo">
        ///     The version the update is updating to.
        /// </param>
        /// <param name="updaterCacheDirectory">
        ///     The path to the directory containing the application updater cache.
        /// </param>
        /// <param name="applicationDirectory">
        ///     The path to the application directory.
        /// </param>
        public Update(Version versionFrom, Version versionTo, string updaterCacheDirectory, string applicationDirectory)
            : this(versionFrom, versionTo)
        {
            if (updaterCacheDirectory == null)
            {
                throw new ArgumentNullException("updaterCacheDirectory");
            }

            if (applicationDirectory == null)
            {
                throw new ArgumentNullException("applicationDirectory");
            }

            this.UpdaterCacheDirectory = updaterCacheDirectory;
            this.ApplicationDirectory = applicationDirectory;
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="Update" /> class.
        /// </summary>
        /// <param name="versionFrom">
        ///     The version the update should be applied to.
        /// </param>
        /// <param name="versionTo">
        ///     The version the update is updating to.
        /// </param>
        private Update(Version versionFrom, Version versionTo)
        {
            if (versionTo == null)
            {
                throw new ArgumentNullException("versionTo");
            }

            this.VersionFrom = versionFrom;
            this.VersionTo = versionTo;

            this.Sequence = new List<IUpdateCommand>();
        }

        /// <summary>
        ///     Gets or sets a path to the application directory.
        /// </summary>
        /// <value>
        ///     The application directory.
        /// </value>
        public string ApplicationDirectory { get; set; }

        /// <summary>
        ///     Gets the sequence of update commands applying the update.
        /// </summary>
        public List<IUpdateCommand> Sequence { get; private set; }

        /// <summary>
        ///     Gets a path to the directory containing the unpacked files of the update.
        /// </summary>
        public string UnpackedUpdateDirectory
        {
            get
            {
                return Path.Combine(this.UpdaterCacheDirectory, this.VersionTo.ToString());
            }
        }

        /// <summary>
        ///     Gets a path to the archived file containing the update.
        /// </summary>
        public string UpdateArchiveFilepath
        {
            get
            {
                return Path.Combine(this.UpdaterCacheDirectory, this.VersionTo + ".zip");
            }
        }

        /// <summary>
        ///     Gets or sets a path to the directory containing the application updater cache.
        /// </summary>
        /// <value>
        ///     The updater cache directory.
        /// </value>
        public string UpdaterCacheDirectory { get; set; }

        /// <summary>
        ///     Gets a version the update should be applied to.
        /// </summary>
        public Version VersionFrom { get; private set; }

        /// <summary>
        ///     Gets a version the update is updating to.
        /// </summary>
        public Version VersionTo { get; private set; }

        /// <summary>
        ///     Initializes a new instance of the <see cref="Update" /> class with meta information of an update XML-file.
        /// </summary>
        /// <param name="stream">
        ///     The XML-file stream.
        /// </param>
        /// <returns>
        ///     A new instance of the <see cref="Update" /> class.
        /// </returns>
        public static Update FromXml(Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            XDocument xdoc = XDocument.Load(stream);
            var schemas = new XmlSchemaSet();
            Stream xsdStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Updater.update-meta.xsd");
            schemas.Add("", XmlReader.Create(xsdStream));
            xdoc.Validate(schemas, null);

            string versionFromStr = xdoc.Root.Attribute("version-from").Value;
            Version versionFrom = versionFromStr != "*" ? Version.Parse(versionFromStr) : null;
            Version versionTo = Version.Parse(xdoc.Root.Attribute("version-to").Value);
            IUpdateCommand[] commands = xdoc.Root.Element("commands").Elements().Select(createCommand).ToArray();
            var update = new Update(versionFrom, versionTo);
            update.Sequence.AddRange(commands);
            return update;
        }

        /// <summary>
        ///     Executes the specified execution stage of the update.
        /// </summary>
        /// <param name="executionStage">
        ///     The execution stage.
        /// </param>
        public void Execute()
        {
            /*switch (executionStage)
            {
            case UpdateExecutionStage.Background:
                foreach (IUpdateCommand command in Sequence)
                {
                    command.Prepare(this);
                }

                break;
            case UpdateExecutionStage.LauncherStartup:
                foreach (IUpdateCommand command in Sequence)
                {
                    command.ExecuteLauncherStartupActions(this);
                }

                break;
            default:
                throw new ArgumentOutOfRangeException();
            }*/
        }

        private static IUpdateCommand createCommand(XElement element)
        {
            string name = element.Name.LocalName;
            if (name == "add-file")
            {
                string filename = element.Attribute("name").Value;
                return new AddFileCommand(filename);
            }

            if (name == "delete-file")
            {
                string filename = element.Attribute("name").Value;
                return new DeleteFileCommand(filename);
            }

            if (name == "vcdiff-patch-file")
            {
                string filename = element.Attribute("name").Value;
                byte[] sourceSha1 = Convert.FromBase64String(element.Attribute("source-sha1").Value);
                byte[] targetSha1 = Convert.FromBase64String(element.Attribute("target-sha1").Value);
                //return new VcdiffPatchFileCommand(filename, sourceSha1, targetSha1);
            }

            throw new UpdaterException("Invalid command tag found in the update meta file.");
        }
    }
}
